package com.leyou.order.service;

import com.leyou.common.auth.utils.UserHolder;
import com.leyou.common.constants.LyConstants;
import com.leyou.common.exception.LyException;
import com.leyou.common.exception.enums.ExceptionEnum;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.IdWorker;
import com.leyou.item.client.ItemClient;
import com.leyou.item.entity.Sku;
import com.leyou.order.dto.CartDTO;
import com.leyou.order.dto.OrderDTO;
import com.leyou.order.entity.Order;
import com.leyou.order.entity.OrderDetail;
import com.leyou.order.entity.OrderLogistics;
import com.leyou.order.enums.OrderStatusEnum;
import com.leyou.order.mapper.OrderDetailMapper;
import com.leyou.order.mapper.OrderLogisticsMapper;
import com.leyou.order.mapper.OrderMapper;
import com.leyou.order.utils.PayHelper;
import com.leyou.order.vo.OrderDetailVO;
import com.leyou.order.vo.OrderLogisticsVO;
import com.leyou.order.vo.OrderVO;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.AddressDTO;
//import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@Transactional
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private OrderLogisticsMapper logisticsMapper;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private ItemClient itemClient;

    @Autowired
    private UserClient userClient;

    @Autowired
    private PayHelper payHelper;

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 创建订单接口
     * @param orderDTO          接收请求的参数
     * @return                  返回订单ID
     */
    //@GlobalTransactional
    @Transactional  // 这里有事务
    public Long createOrder(OrderDTO orderDTO) {
        // 0、生成订单的ID： 雪花ID
        long orderId = idWorker.nextId();
        Long userId = UserHolder.getUser().getId(); // 当前登录用户的id
        // 1、保存订单
        Order order = new Order();
        // 1.1 设置有值的
        order.setOrderId(orderId);
        order.setPostFee(0L); // 乐优大卖场全场包邮
        order.setPaymentType(orderDTO.getPaymentType());//支付类型
        order.setUserId(userId);// 当前用户
        order.setStatus(OrderStatusEnum.INIT.value()); // 订单状态
        order.setInvoiceType(0); // 无发票
        order.setSourceType(2);// 来自pc端
        // =============================================
        // 1.2 设置经过计算得出的值 : 得出每个sku的价格
        // 1.2.1 获取购物车数据
        List<CartDTO> carts = orderDTO.getCarts();
        // 为了方便我们后面通过skuid获取购买的商品的数量，我们把list集合转成map
        Map<Long, Integer> cartMap = carts.stream().collect(Collectors.toMap(CartDTO::getSkuId, CartDTO::getNum));
        // 1.2.2 收集所有的sku的ID
        List<Long> skuIdList = carts.stream().map(CartDTO::getSkuId).collect(Collectors.toList());
        // 1.2.3 批量查询sku
        List<Sku> skuList = itemClient.findSkuListByIds(skuIdList);
        if(CollectionUtils.isEmpty(skuList)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        // 1.2.4 计算总金额
        long total = skuList.stream().mapToLong(sku -> sku.getPrice() * cartMap.get(sku.getId())).sum();
        /*
        Long total = 0L;
        for (Sku sku : skuList) {
            total += sku.getPrice() * cartMap.get(sku.getId());
        }
        */
        order.setTotalFee(total); //总金额计算得出 ：
        order.setActualFee(1L); // TODO： 实付金额 = 总金额 - 活动优惠的金额 ，现在我们写死一分钱
        // 1.3 保存
        int count = orderMapper.insertSelective(order);
        if (count != 1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }

        // 2、保存订单的条目： 批量插入
        List<OrderDetail> orderDetailList = new ArrayList<>();
        for (Sku sku : skuList) {
            OrderDetail od = new OrderDetail();
            od.setId(idWorker.nextId()); // 雪花id
            od.setOrderId(orderId);// 订单id
            od.setSkuId(sku.getId()); // skuid
            od.setNum(cartMap.get(sku.getId())); // 购买量
            od.setTitle(sku.getTitle());
            od.setPrice(sku.getPrice());
            od.setOwnSpec(sku.getOwnSpec());
            od.setImage(StringUtils.substringBefore(sku.getImages(),","));
            od.setCreateTime(new Date());
            od.setUpdateTime(new Date());
            // 有些同学会忘了这一步
            orderDetailList.add(od);
        }
        count = orderDetailMapper.insertList(orderDetailList);
        if(count != orderDetailList.size()){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        // 3、保存订单的物流表信息
        // 3.1 查询地址
        AddressDTO addressDTO = userClient.queryAddressById(userId, orderDTO.getAddressId());
        // 3.2 创建物流对象
        OrderLogistics orderLogistics = BeanHelper.copyProperties(addressDTO, OrderLogistics.class);
        orderLogistics.setOrderId(orderId);
        // 3.3 保存物流信息
        count = logisticsMapper.insertSelective(orderLogistics);
        if (count != 1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
        // 4、减去库存 ： 使用的是feign的远程调用，feign的远程调用，如果接口出现异常，会把异常返回到当前方法
        // FeignException  : 是一个运行时异常，由于是运行时异常，事务注解标记，如果这里出现了异常，整个方法就回滚了
        // 事务原子性： 整个方法全部回滚

        // 模拟异常： 出现位置1： 在远程调用之前 : 这里不会有问题
        // int i = 1/0;

        itemClient.minusStock(cartMap);  // 执行如果成功，那么数据就已经持久化了

        // 模拟异常： 出现位置12： 在远程调用之后 : 这里会有问题： 这个方法的订单数据会回滚，但是库存回滚不了
        // 解决方案： 使用分布式事务去解决这个问题

        // 分布式事务，万不得已的时候才去用，用了分布式事务性能会下降
        //int i = 1/0;


        // 5、返回订单的ID:雪花ID
        //遗留功能：清空购物车。什么时候清空？下单【对卖家不友好】，支付【对买家不友好】。
        return orderId;
    }

    /**
     * 根据订单编号查询订单
     * @param orderId 订单编号
     * @return        OrderVO对象
     */
    public OrderVO findOrderById(Long orderId) {
        // 1、查询订单
        Order order = orderMapper.selectByPrimaryKey(orderId);
        // 1.1 判空
        if(order == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 1.2 判断订单是否是当前用户的
        Long userId = UserHolder.getUser().getId();
        if(!StringUtils.equals(userId.toString(), order.getUserId().toString())){
            throw new LyException(ExceptionEnum.ORDER_ERROR);
        }
        // 1.3 把Order转成Vo
        OrderVO orderVO = BeanHelper.copyProperties(order, OrderVO.class);
        // 2、查询物流表
        OrderLogistics orderLogistics = logisticsMapper.selectByPrimaryKey(orderId);
        if(orderLogistics == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 3、查询订单条目
        OrderDetail record = new OrderDetail();
        record.setOrderId(orderId);
        List<OrderDetail> orderDetailList = orderDetailMapper.select(record);
        if(CollectionUtils.isEmpty(orderDetailList)){
            throw new LyException(ExceptionEnum.ORDER_DETAIL_NOT_FOUND);
        }
        // 4、封装到vo中并返回
        orderVO.setLogistics(BeanHelper.copyProperties(orderLogistics, OrderLogisticsVO.class));
        orderVO.setDetailList(BeanHelper.copyWithCollection(orderDetailList, OrderDetailVO.class));
        return orderVO;
    }

    /**
     * 获取微信支付的链接
     * @param orderId
     * @return
     */
    public String findPayUrl(Long orderId) {
        // 0、先去redis中查询，没有这个url，再去微信获取
        String key = LyConstants.PAY_URL_PRE + orderId;
        try {
            Boolean bool = redisTemplate.hasKey(key);
            if(bool!=null && bool){
                return redisTemplate.opsForValue().get(key);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("【订单微服务】redis已经挂了，管理员赶紧看下哦，不然扣工资！");
        }

        // 1、查询订单信息
        Order order = orderMapper.selectByPrimaryKey(orderId);
        // 1.1 判空
        if(order == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 1.2 判断订单的状态
        if(!(OrderStatusEnum.INIT.value() == order.getStatus())){
            throw new LyException(ExceptionEnum.INVALID_ORDER_STATUS);
        }
        // 2、查询订单详情
        OrderDetail record = new OrderDetail();
        record.setOrderId(orderId);
        List<OrderDetail> orderDetails = orderDetailMapper.select(record);
        // 2.1 判空
        if(CollectionUtils.isEmpty(orderDetails)){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 2.2 拼接描述信息
        String desc = orderDetails.stream().map(OrderDetail::getTitle).collect(Collectors.joining());
        if(StringUtils.isNotBlank(desc)){
            if(desc.length() > 50){
                desc = StringUtils.substring(desc, 0, 50);
            }
        }
        // 3、支付的url获取
        String payUrl = payHelper.getPayUrl(orderId, order.getActualFee(), desc);

        // 4、获取到url之后，存入redis中
        try{
            redisTemplate.opsForValue().set(key, payUrl, 2, TimeUnit.HOURS);
        }catch (Exception e){
            e.printStackTrace();
            log.error("【订单微服务】redis已经挂了，管理员赶紧看下哦，不然扣工资！");
        }
        return payUrl;
    }

    /**
     * 微信通知支付结果
     * @param msg
     */
    public void payNotify(Map<String, String> msg) {
        // 1、校验是否是成功
        payHelper.checkResultCode(msg);
        // 2、校验签名
        payHelper.checkSignature(msg);
        // 3、获取微信返回的乐优的订单号和订单金额
        Long orderId = Long.valueOf(msg.get("out_trade_no"));
        Long total_fee = Long.valueOf(msg.get("total_fee"));
        // 4、获取订单
        Order order = orderMapper.selectByPrimaryKey(orderId);
        if(order == null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        // 5、接口的幂等性校验： N请求过来，结果一样
        if(order.getStatus() != OrderStatusEnum.INIT.value()){
            throw new LyException(ExceptionEnum.INVALID_ORDER_STATUS);
        }

        // 6、判断订单的金额是否一致
        if(!order.getActualFee().equals(total_fee)){
            throw new LyException("订单金额不一致",400);
        }
        // 7、完成状态的更改
        order.setStatus(OrderStatusEnum.PAY_UP.value());
        int count = orderMapper.updateByPrimaryKeySelective(order);
        if(count != 1){
            throw new LyException(ExceptionEnum.UPDATE_OPERATION_FAIL);
        }
        log.info("【微信支付成功】订单号：{} , 总金额:{}", orderId, total_fee);

    }

    /**
     * 查询订单的状态
     * @param orderId
     * @return
     */
    public Integer findOrderState(Long orderId) {
        Order order = orderMapper.selectByPrimaryKey(orderId);
        if(order==null){
            throw new LyException(ExceptionEnum.ORDER_NOT_FOUND);
        }
        return order.getStatus();
    }
}
